/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  [DebuggerDisplay( "Length = {Length}" )]
  internal class ListDataItemIndexLeaf : ListDataItemIndexNode
  {
    #region Constructor

    internal ListDataItemIndexLeaf(
      ListDataItemIndexConfiguration configuration,
      IEqualityComparer<StatFunction> statFunctionComparer,
      int level, 
      int indexStart,
      int length,
      Func<int, int, RawDataItem, IComparer<RawDataItem>, int> binarySearch )
      : base( configuration, statFunctionComparer, level, indexStart, length )
    {
      if( configuration == null )
        throw new ArgumentNullException( "configuration" );

      if( level < 0 || level >= configuration.SortOrder.Count )
        throw new ArgumentOutOfRangeException( "level" );

      if( binarySearch == null )
        throw new ArgumentNullException( "binarySearch" );

      m_memberName = configuration.SortOrder[ level ].PropertyName;
      m_binarySearch = binarySearch;
    }

    #endregion

    internal override void Add( LinkedListNode<object> groupValue )
    {
      if( groupValue == null )
        throw new ArgumentNullException( "groupValue" );

      this.Grow();
    }

    internal override bool Remove( int index )
    {
      // We must remove the current empty node.
      if( this.Length <= 1 )
        return true;

      this.Shrink();
      return false;
    }

    internal override int GetDataItemIndex(
      LinkedListNode<object> groupValue, 
      bool reverseOrder,
      bool skipStartingItem )
    {
      int index;
      this.FindDataItemIndex( groupValue, reverseOrder, skipStartingItem, out index );

      return index;
    }

    internal override bool GetGroupCount( LinkedListNode<int> groupCount )
    {
      if( groupCount != null )
      {
        groupCount.Value += this.Length;
      }

      return true;
    }

    internal override bool GetGroupCount( 
      LinkedListNode<int> groupCount, 
      LinkedListNode<object> groupValue )
    {
      // All items on this level must be counted.
      if( groupValue == null )
        return this.GetGroupCount( groupCount );

      // The item wasn't found, so it is not counted.
      int index;
      if( !this.FindDataItemIndex( groupValue, false, false, out index ) )
        return false;

      // The item was found, we must count it.
      if( groupCount != null )
      {
        groupCount.Value += 1;
      }

      return true;
    }

    internal override bool GetGroupCount( 
      LinkedListNode<int> groupCount, 
      LinkedListNode<object> groupValue, 
      bool reverseOrder, 
      bool skipStartingItem )
    {
      // All items on this level must be counted.
      if( ( groupCount == null ) || ( groupValue == null ) )
        return this.GetGroupCount( groupCount );

      int index;
      this.FindDataItemIndex( groupValue, reverseOrder, skipStartingItem, out index );

      // The item we were looking for isn't part of the group.
      if( ( index < this.IndexStart ) || ( index >= this.IndexStart + this.Length ) )
        return false;

      groupCount.Value += ( reverseOrder )
                            ? index - this.IndexStart + 1
                            : this.IndexStart + this.Length - index;

      return true;
    }

    internal override bool GetGroupCount(
      LinkedListNode<int> groupCount,
      LinkedListNode<object> filterGroupValue,
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem )
    {
      if( filterGroupValue == null )
        return this.GetGroupCount( groupCount, groupValue, reverseOrder, skipStartingItem );

      if( groupCount == null )
        return this.GetGroupCount( groupCount );

      int index;
      bool found = this.FindDataItemIndex( filterGroupValue, reverseOrder, skipStartingItem, out index );

      // The group doesn't exists.
      if( !found )
        return false;

      int startIndex;
      this.FindDataItemIndex( groupValue, reverseOrder, skipStartingItem, out startIndex );

      // The starting item has no impact on the group count.
      if( ( ( !reverseOrder ) && ( startIndex > index ) )
        || ( ( reverseOrder ) && ( startIndex < index ) ) )
        return false;

      groupCount.Value += 1;

      return true;
    }

    private bool FindDataItemIndex(
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem,
      out int index )
    {
      if( groupValue == null )
      {
        if( skipStartingItem )
        {
          index = ( reverseOrder )
                    ? this.IndexStart - 1
                    : this.IndexStart + this.Length;
        }
        else
        {
          index = ( reverseOrder )
                    ? this.IndexStart + this.Length - 1
                    : this.IndexStart;
        }

        return true;
      }

      TargetValue targetValue = new TargetValue( groupValue.Value );
      MemberValueComparer comparer = new MemberValueComparer( m_memberName, this.ItemComparer );
      index = m_binarySearch.Invoke( this.IndexStart, this.Length, targetValue, comparer );

      // The item was found.
      if( index >= 0 )
      {
        if( skipStartingItem )
        {
          index += ( reverseOrder ) ? -1 : 1;
        }

        return true;
      }
      else
      {
        // Find out the position of the item if it would have exist.
        index = ~index;

        // In reverse order, we must return the previous index since it is the
        // position of the closest element to the one we've look for.
        if( reverseOrder )
        {
          index--;
        }

        return false;
      }
    }

    #region Private Fields

    private string m_memberName;
    private Func<int, int, RawDataItem, IComparer<RawDataItem>, int> m_binarySearch;

    #endregion

    #region Private TargetValue Nested Type

    private class TargetValue : RawDataItem
    {
      internal TargetValue( object value )
        : base( value )
      {
      }

      protected override bool FindDataMemberValue( string memberName, out object value )
      {
        value = this.Data;
        return true;
      }
    }

    #endregion

    #region Private MemberValueComparer Nested Type

    private class MemberValueComparer : IComparer<RawDataItem>
    {
      internal MemberValueComparer( string memberName, IComparer comparer )
      {
        if( comparer == null )
          throw new ArgumentNullException( "comparer" );

        m_memberName = memberName;
        m_comparer = comparer;
      }

      public int Compare( RawDataItem x, RawDataItem y )
      {
        if( ( x == null ) || ( y == null ) )
          throw new DataInternalException();

        object firstValue = x.GetDataMemberValue( m_memberName );
        object secondValue = y.GetDataMemberValue( m_memberName );

        return m_comparer.Compare( firstValue, secondValue );
      }

      private string m_memberName;
      private IComparer m_comparer;
    }

    #endregion
  }
}
